问题描述
八数码问题,简单地来描述是这样的:在一个九宫格内,填有1、2、3、4、5、6、7、8,八个阿拉伯数字,有一个格子为空白。就下面这样,这是一个没有归位的九宫格。
1 | 2 | 3 |
---|---|---|
7 | 8 | |
4 | 6 | 5 |
(未归位的九宫格)
对于上面这个九宫格,我们要通过移动数字来使之归位,每次移动都只能是与空白格相邻的数字移到空白格里面。最终得到归位的九宫格应该是像下面这样的。
1 | 2 | 3 |
---|---|---|
8 | 4 | |
7 | 6 | 5 |
(已归位的九宫格)
我们的目标是,给出一种初始状态,如果有解的话,就把移动的步骤记录(或者输出)下来。值得注意的是,有的初始状态,是无论如何都没有办法使九宫格归位的。
解题思路
问题抽象
对于这个问题,我们不妨这样来看:每一种状态,最多有四种移动方式,也就是说,一种状态最多可以衍生出四种子状态。我们把问题的整体看成一个图,一个状态是图中的一个节点,每个节点生成的自节点由一条有向边来指向。图中总有一个节点的状态是我们要的归位状态,我们要做的就是找到初始节点(即初状态)到这个归位节点的路径。
图的宽度优先搜索
为了找到我们要的路径,我们可以使用宽度优先搜索。我们需要三样东西:集合close
,用于保存访问过的节点。集合open
,用于保存待访问的节点。每次访问一个节点,就会产生子节点,那么有如下三种情况:
如果子节点存在于
open
中,就把该子节点的深度与open
中的那个进行对比,若该子节点的深度较小,就替换。如果子节点存在于
close
中,就把该子节点的深度与close
中的那个进行对比,若该子节点的深度较小,就替换,并且该子节点的所有后代节点深度都要进行修改。(但是在该问题的A*搜索算法中,我们可以不用考虑这件事。把弱遇到子节点存在于close
中的情况,直接将其扔掉就可以了)如果子节点不在
open
中,也不在close
,那么将该节点加入到open
中等待访问。
如此进行下去,一直到找到我们想要的节点(说明有解),或者open
变为空为止(说明无解)。
A*搜索
如果问题有解的话,有没有什么办法可以让我们的搜索变得更快呢?
有,我们可以设置一个评估函数F(n) = G(n) + H(n)
,G(n)
给出的是节点的深度。H(n)
它给出的是当前状态下将所有数字归位所需的最小步数。每次要从open
中取出一个节点的时候,按照F(n)
给节点拍一个序,然后选取函数值最小的节点。评估函数H(n)
所给出的步数,比实际让九宫格归位所需的步数要少,所以这种启发式搜索叫做A*搜索。
代码实现
我们使用一个整数表示一种状态,整数中的每一位对应了九宫格中的一位,这样可以缩减比较九宫格是否一样所使用的时间。
比如九宫格 :
1 | 2 | 3 |
---|---|---|
7 | 8 | |
6 | 5 | 4 |
被表示为整数123780654。
数据结构
每一个节点里面的信息包含:
state
:当前的状态、
prev
:父节点的指针、
next
:一个存放子节点指针的向量、
cost
:H(n)
的函数值、
zeropos
:空白格子的位置、
depth
:节点深度。
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
#ifndef NODE_H
#define NODE_H
struct Node{
int state;
Node* prev;
vector<Node*> next;
int cost;
int zeropos;
int dept;
};
#endif
change
函数
输入一个状态,以及空白格子的位置,以及空白格子的移动方向,如果移动方向合法,就可以产生子状态,否则返回-1.
int change(int state,int zeropos,int direction){
int ansstate = 0;
int usestate = state;
int zerorow = (zeropos-1)/3;
int zerocol =